/***
 * Copyright (C) Microsoft. All rights reserved.
 * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 *
 * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
 *
 * Protocol independent support for URIs.
 *
 * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
 *
 * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 ****/

#pragma once

#include "compat.h"
#include <map>
#include <string>
#include <utility>
#include <vector>

namespace Pistache
{
namespace Details
{
struct UriComponents
{
	UriComponents() : m_path(("/")), m_port(-1) {}

	UriComponents(const UriComponents&) = default;
	UriComponents& operator=(const UriComponents&) = default;

	// This is for VS2013 compatibility -- replace with '= default' when VS2013 is completely dropped.
	UriComponents(UriComponents&& other) _NOEXCEPT : m_scheme(std::move(other.m_scheme)),
		m_host(std::move(other.m_host)),
	m_user_info(std::move(other.m_user_info)),
	m_path(std::move(other.m_path)),
	m_query(std::move(other.m_query)),
	m_fragment(std::move(other.m_fragment)),
	m_port(other.m_port)
	{
	}

	// This is for VS2013 compatibility -- replace with '= default' when VS2013 is completely dropped.
	UriComponents& operator=(UriComponents&& other) _NOEXCEPT
	{
		if (this != &other)
		{
			m_scheme = std::move(other.m_scheme);
			m_host = std::move(other.m_host);
			m_user_info = std::move(other.m_user_info);
			m_path = std::move(other.m_path);
			m_query = std::move(other.m_query);
			m_fragment = std::move(other.m_fragment);
			m_port = other.m_port;
		}
		return *this;
	}

	std::string join();

	std::string m_scheme;
	std::string m_host;
	std::string m_user_info;
	std::string m_path;
	std::string m_query;
	std::string m_fragment;
	int m_port;
};
} // namespace details

/// <summary>
/// A single exception type to represent errors in parsing, encoding, and decoding URIs.
/// </summary>
class UriException : public std::exception
{
public:
	explicit UriException(std::string msg) : m_msg(std::move(msg)) {}

	~UriException() _NOEXCEPT override = default;

	_ASYNCRTIMP const char* what() const _NOEXCEPT override { return m_msg.c_str(); }

private:
	std::string m_msg;
};

/// <summary>
/// A flexible, protocol independent URI implementation.
///
/// URI instances are immutable. Querying the various fields on an empty URI will return empty strings. Querying
/// various diagnostic members on an empty URI will return false.
/// </summary>
/// <remarks>
/// This implementation accepts both URIs ('http://msn.com/path') and URI relative-references
/// ('/path?query#frag').
///
/// This implementation does not provide any scheme-specific handling -- an example of this
/// would be the following: 'http://path1/path'. This is a valid URI, but it's not a valid
/// http-Uri -- that is, it's syntactically correct but does not conform to the requirements
/// of the http scheme (http requires a host).
/// We could provide this by allowing a pluggable 'scheme' policy-class, which would provide
/// extra capability for validating and canonicalizing a URI according to scheme, and would
/// introduce a layer of type-safety for URIs of differing schemes, and thus differing semantics.
///
/// One issue with implementing a scheme-independent URI facility is that of comparing for equality.
/// For instance, these URIs are considered equal 'http://msn.com', 'http://msn.com:80'. That is --
/// the 'default' port can be either omitted or explicit. Since we don't have a way to map a scheme
/// to it's default port, we don't have a way to know these are equal. This is just one of a class of
/// issues with regard to scheme-specific behavior.
/// </remarks>
class _ASYNCRTIMP Uri
{
public:
	/// <summary>
	/// The various Components of a URI. This enum is used to indicate which
	/// URI component is being encoded to the encode_uri_component. This allows
	/// specific encoding to be performed.
	///
	/// Scheme and port don't allow '%' so they don't need to be encoded.
	/// </summary>
	class Components
	{
	public:
		enum component
		{
			user_info,
			host,
			path,
			query,
			fragment,
			full_uri
		};
	};

	/// <summary>
	/// Encodes a URI component according to RFC 3986.
	/// Note if a full URI is specified instead of an individual URI component all
	/// characters not in the unreserved set are escaped.
	/// </summary>
	/// <param name="raw">The URI as a string.</param>
	/// <returns>The encoded string.</returns>
	static std::string __cdecl encode_uri(const std::string& raw,
		Uri::Components::component = Components::full_uri);

	/// <summary>
	/// Encodes a string by converting all characters except for RFC 3986 unreserved characters to their
	/// hexadecimal representation.
	/// </summary>
	/// <returns>The encoded string.</returns>
	static std::string __cdecl encode_data_string(const std::string& data);

	/// <summary>
	/// Decodes an encoded string.
	/// </summary>
	/// <param name="encoded">The URI as a string.</param>
	/// <returns>The decoded string.</returns>
	static std::string __cdecl decode(const std::string& encoded);

	/// <summary>
	/// Splits a path into its hierarchical Components.
	/// </summary>
	/// <param name="path">The path as a string</param>
	/// <returns>A <c>std::vector&lt;std::string&gt;</c> containing the segments in the path.</returns>
	static std::vector<std::string> __cdecl split_path(const std::string& path);

	/// <summary>
	/// Splits a query into its key-value Components.
	/// </summary>
	/// <param name="query">The query string</param>
	/// <returns>A <c>std::map&lt;std::string, std::string&gt;</c> containing the key-value Components of
	/// the query.</returns>
	static std::map<std::string, std::string> __cdecl split_query(
		const std::string& query);

	/// <summary>
	/// Validates a string as a URI.
	/// </summary>
	/// <remarks>
	/// This function accepts both uris ('http://msn.com') and Uri relative-references ('path1/path2?query').
	/// </remarks>
	/// <param name="uri_string">The URI string to be validated.</param>
	/// <returns><c>true</c> if the given string represents a valid URI, <c>false</c> otherwise.</returns>
	static bool __cdecl validate(const std::string& uri_string);

	/// <summary>
	/// Creates an empty Uri
	/// </summary>
	Uri() : m_uri(("/")) {}

	/// <summary>
	/// Creates a URI from the given encoded string. This will throw an exception if the string
	/// does not contain a valid URI. Use Uri::validate if processing user-input.
	/// </summary>
	/// <param name="uri_string">A pointer to an encoded string to create the URI instance.</param>
	explicit Uri(const char* uri_string);

	/// <summary>
	/// Creates a URI from the given encoded string. This will throw an exception if the string
	/// does not contain a valid URI. Use Uri::validate if processing user-input.
	/// </summary>
	/// <param name="uri_string">An encoded URI string to create the URI instance.</param>
	explicit Uri(const std::string& uri_string);

	/// <summary>
	/// Copy constructor.
	/// </summary>
	Uri(const Uri&) = default;

	/// <summary>
	/// Copy assignment operator.
	/// </summary>
	Uri& operator=(const Uri&) = default;

	/// <summary>
	/// Move constructor.
	/// </summary>
	// This is for VS2013 compatibility -- replace with '= default' when VS2013 is completely dropped.
	Uri(Uri&& other) _NOEXCEPT : m_uri(std::move(other.m_uri)), m_components(std::move(other.m_components)) {}

	/// <summary>
	/// Move assignment operator
	/// </summary>
	// This is for VS2013 compatibility -- replace with '= default' when VS2013 is completely dropped.
	Uri& operator=(Uri&& other) _NOEXCEPT
	{
		if (this != &other)
		{
			m_uri = std::move(other.m_uri);
			m_components = std::move(other.m_components);
		}
		return *this;
	}

	/// <summary>
	/// Get the scheme component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI scheme as a string.</returns>
	const std::string& scheme() const { return m_components.m_scheme; }

	/// <summary>
	/// Get the user information component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI user information as a string.</returns>
	const std::string& user_info() const { return m_components.m_user_info; }

	/// <summary>
	/// Get the host component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI host as a string.</returns>
	const std::string& host() const { return m_components.m_host; }

	/// <summary>
	/// Get the port component of the URI. Returns -1 if no port is specified.
	/// </summary>
	/// <returns>The URI port as an integer.</returns>
	int port() const { return m_components.m_port; }

	/// <summary>
	/// Get the path component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI path as a string.</returns>
	const std::string& path() const { return m_components.m_path; }

	/// <summary>
	/// Get the query component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI query as a string.</returns>
	const std::string& query() const { return m_components.m_query; }

	/// <summary>
	/// Get the fragment component of the URI as an encoded string.
	/// </summary>
	/// <returns>The URI fragment as a string.</returns>
	const std::string& fragment() const { return m_components.m_fragment; }

	/// <summary>
	/// Creates a new Uri object with the same authority portion as this one, omitting the resource and query portions.
	/// </summary>
	/// <returns>The new Uri object with the same authority.</returns>
	Uri authority() const;

	/// <summary>
	/// Gets the path, query, and fragment portion of this Uri, which may be empty.
	/// </summary>
	/// <returns>The new URI object with the path, query and fragment portion of this URI.</returns>
	Uri resource() const;

	/// <summary>
	/// An empty URI specifies no Components, and serves as a default value
	/// </summary>
	bool is_empty() const { return this->m_uri.empty() || this->m_uri == ("/"); }

	/// <summary>
	/// A loopback URI is one which refers to a hostname or ip address with meaning only on the local machine.
	/// </summary>
	/// <remarks>
	/// Examples include "localhost", or ip addresses in the loopback range (127.0.0.0/24).
	/// </remarks>
	/// <returns><c>true</c> if this URI references the local host, <c>false</c> otherwise.</returns>
	bool is_host_loopback() const
	{
		return !is_empty() &&
			   ((host() == ("localhost")) || (host().size() > 4 && host().substr(0, 4) == ("127.")));
	}

	/// <summary>
	/// A wildcard URI is one which refers to all hostnames that resolve to the local machine (using the * or +)
	/// </summary>
	/// <example>
	/// http://*:80
	/// </example>
	bool is_host_wildcard() const
	{
		return !is_empty() && (this->host() == ("*") || this->host() == ("+"));
	}

	/// <summary>
	/// A portable URI is one with a hostname that can be resolved globally (used from another machine).
	/// </summary>
	/// <returns><c>true</c> if this URI can be resolved globally (used from another machine), <c>false</c>
	/// otherwise.</returns> <remarks> The hostname "localhost" is a reserved name that is guaranteed to resolve to the
	/// local machine, and cannot be used for inter-machine communication. Likewise the hostnames "*" and "+" on Windows
	/// represent wildcards, and do not map to a resolvable address.
	/// </remarks>
	bool is_host_portable() const { return !(is_empty() || is_host_loopback() || is_host_wildcard()); }

	/// <summary>
	/// A default port is one where the port is unspecified, and will be determined by the operating system.
	/// The choice of default port may be dictated by the scheme (http -> 80) or not.
	/// </summary>
	/// <returns><c>true</c> if this URI instance has a default port, <c>false</c> otherwise.</returns>
	bool is_port_default() const { return !is_empty() && this->port() == 0; }

	/// <summary>
	/// An "authority" URI is one with only a scheme, optional userinfo, hostname, and (optional) port.
	/// </summary>
	/// <returns><c>true</c> if this is an "authority" URI, <c>false</c> otherwise.</returns>
	bool is_authority() const { return !is_empty() && is_path_empty() && query().empty() && fragment().empty(); }

	/// <summary>
	/// Returns whether the other URI has the same authority as this one
	/// </summary>
	/// <param name="other">The URI to compare the authority with.</param>
	/// <returns><c>true</c> if both the URI's have the same authority, <c>false</c> otherwise.</returns>
	bool has_same_authority(const Uri& other) const { return !is_empty() && this->authority() == other.authority(); }

	/// <summary>
	/// Returns whether the path portion of this URI is empty
	/// </summary>
	/// <returns><c>true</c> if the path portion of this URI is empty, <c>false</c> otherwise.</returns>
	bool is_path_empty() const { return path().empty() || path() == ("/"); }

	/// <summary>
	/// Returns the full (encoded) URI as a string.
	/// </summary>
	/// <returns>The full encoded URI string.</returns>
	std::string to_string() const { return m_uri; }

	/// <summary>
	/// Returns an URI resolved against <c>this</c> as the base URI
	/// according to RFC3986, Section 5 (https://tools.ietf.org/html/rfc3986#section-5).
	/// </summary>
	/// <param name="relativeUri">The relative URI to be resolved against <c>this</c> as base.</param>
	/// <returns>The new resolved URI string.</returns>
	std::string resolve_uri(const std::string& relativeUri) const;

	bool operator==(const Uri& other) const;

	bool operator<(const Uri& other) const { return m_uri < other.m_uri; }

	bool operator!=(const Uri& other) const { return !(this->operator==(other)); }

private:
	friend class UriBuilder;

	/// <summary>
	/// Creates a URI from the given URI Components.
	/// </summary>
	/// <param name="Components">A URI Components object to create the URI instance.</param>
	explicit Uri(const Details::UriComponents& Components);

	// Used by UriBuilder
	static std::string __cdecl encode_query_impl(const std::string& raw);

	std::string m_uri;
	Details::UriComponents m_components;
};

}
